package com.fengwk.deeplearning.study;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import com.fengwk.support.util.IoUtils;

/**
 * 逻辑回归训练,非向量实现
 * 
 * @author fengwk
 *
 */
public class Study01_Logistic implements Serializable {

	private static final long serialVersionUID = -6366524849867339438L;
	
	private Map<double[], Double> sample;// 训练样本map,装载特征xs和结果y
	protected double alpha;// 学习率
	protected double threshold;// 梯度阈值
	
	private double[] ws;// 权重
	protected double b;// 偏移量
	protected double J;// 成本
	
	protected int n;
	protected int m;
	
	/**
	 * 构建逻辑回归训练
	 * 
	 * @param xsys 训练样本map,装载特征xs和结果y
	 * @param alpha 学习率
	 * @param threshold 梯度阈值
	 */
	public Study01_Logistic(Map<double[], Double> sample, double alpha, double threshold) {
		this.sample = sample;
		this.alpha = alpha;
		this.threshold = threshold;
		this.n = sample.entrySet().iterator().next().getKey().length;
		this.m = sample.size();
	}
	
	/**
	 * 序列化
	 */
	public void serialize(String filePath) {
		byte[] bytes = IoUtils.objectToBytes(this);
		IoUtils.write(bytes, filePath);
	}
	
	/**
	 * 使用集,必须先进行训练才能使用{@link #training()}
	 * 
	 * @param xs
	 * @return
	 */
	public double run(double[] xs) {
		return sigmoid(z(xs));
	}
	
	/**
	 * 训练集
	 */
	public synchronized void training() {
		init();
		Double J0 = null;
		while (true) {
			gradientDescent();
			if (J0 != null && J - J0 < threshold)
				return;
			J0 = J;
		}
	}
	
	// 初始化
	protected void init() {
		ws = zeros(n);// 重置权重
		b = 0;// 初始化偏移量
	}
	
	// 一次完整的梯度下降
	protected void gradientDescent() {
		J = 0;
		double db = 0;
		double[] dws = zeros(n);
		
		Iterator<Entry<double[], Double>> it = sample.entrySet().iterator();
		while (it.hasNext()) {
			Entry<double[], Double> xsyE = it.next();
			double[] xs = xsyE.getKey();
			double y = xsyE.getValue();
			
			// 正向传播
			double z = z(xs);
			double a = sigmoid(z);
			double loss = loss(a, y);
			J += loss;
			
			// 反向传播
			double dz = a - y;
			for (int i = 0; i < dws.length; i ++) 
				dws[i] += xs[i] * dz;
			db += dz;
		}
		
		// 更新
		J /= m;
		for (int i = 0; i < ws.length; i ++) 
			ws[i] -= alpha * dws[i] / m;
		b -= alpha * db / m;
	}
	
	private double[] zeros(int len) {
		double[] ds = new double[len];// 重置权重
		for (int i = 0; i < ds.length; i ++)
			ds[i] = 0;
		return ds;
	}
	
	// 获取特征与权重的线性值
	private double z(double[] xs) {
		double z = 0;
		for (int i = 0; i < xs.length; i ++)
			z += ws[i] * xs[i];
		return z + b;
	}
	
	// sigmoid函数
	protected double sigmoid(double z) {
		return 1 / (1 + Math.exp(- z));
	}
	
	// 损失函数
	protected double loss(double a, double y) {
		return - (y * Math.log(a) + (1 - y) * Math.log(1 - a));
	}

}
